﻿using System;
using SCG = System.Collections.Generic;

using Nemerle;
using Nemerle.Assertions;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Text;
using Nemerle.Utility;
using PT = Nemerle.Compiler.Parsetree;
using TT = Nemerle.Compiler.Typedtree;

namespace Nemerle.OpenCL {

    [MacroUsage(MacroPhase.BeforeTypedMembers, MacroTargets.Class)]
    public macro InfoProperty(ty : TypeBuilder, params args : array[expr]) {
        def parse_options(options) {
            def result = InfoPropertyOptions();
            foreach(o in options) {
                | <[ name = $(name : name) ]>
                | <[ Name = $(name : name) ]> =>
                    result.CustomName = name;

                | <[ flags( ..$flags ) ]>
                | <[ Flags( ..$flags ) ]> =>
                    foreach(f in flags) {
                        | <[ no_cache ]> | <[ NoCache ]> => 
                            result.NoCache = true;
                        | <[ size_t ]> | <[ SizeT ]> => 
                            result.SizeT = true;
                        | _ => Message.FatalError($"Invalid flag '$f' for InfoProperty macro.")
                    }

                | <[ args( ..$args ) ]>
                | <[ Args( ..$args ) ]> =>
                    result.OptionalArgs = args;

                | _ => Message.FatalError($"Invalid option '$o' for InfoProperty macro.")
            }
            result
        }

        match(NList.ToList(args)) {
            | param_name :: param_type :: options =>
                InfoPropertyImpl.MakeInfoProperty(ty, param_name, param_type, parse_options(options))

            | _ => Message.FatalError("Invalid usage of InfoProperty macro.")
        }
    }

    class InfoPropertyOptions {
        [Accessor(flags = WantSetter)]
        mutable custom_name : PT.Name;

        [Accessor(flags = WantSetter)]
        mutable no_cache : bool = false;

        [Accessor(flags = WantSetter)]
        mutable size_t : bool = false;

        [Accessor(flags = WantSetter)]
        mutable optional_args : list[PT.PExpr] = [];
    }

    module InfoPropertyImpl {

        public MakeInfoProperty(ty : TypeBuilder, param_name : PT.PExpr, param_type : PT.PExpr, opt : InfoPropertyOptions) : void {
            match(param_name) {
                | <[ $_.$enum_ty.$(enum_entry : name) ]>
                | <[ $enum_ty.$(enum_entry : name) ]> =>
                    def prop_name = opt.CustomName ?? enum_entry;

                    def entity = regexp match(enum_ty.ToString()) {
                        | @"^CL(?<entity>\w+)Info$" => entity // Device, Platform, etc.
                        | _ => Message.FatalError("Invalid OpenCL enumeration.")
                    }
                    def param_func = <[ GASS.OpenCL.OpenCLDriver.$("clGet" + entity + "Info" : usesite) ]>;
                    def id = <[ this.$("Id" : usesite) ]>;
                    def get_value = {
                        def args = id :: (opt.OptionalArgs + [ param_name ]);
                        if(opt.SizeT)
                            <[
                                def buffer = BufferedUnsafeCall($param_func(..$args));
                                def ptr : IntPtr = Nemerle.OpenCL.Utils.Converter.Convert(buffer, buffer.Length);
                                ptr :> $param_type
                            ]>
                        else
                            <[
                                def buffer = BufferedUnsafeCall($param_func(..$args));
                                Nemerle.OpenCL.Utils.Converter.Convert(buffer, buffer.Length)
                            ]>
                    };

                    if(opt.NoCache) {
                        //  each call perform evaluation of property value
                        ty.Define(<[ decl:
                            public $(prop_name : name) : $param_type {
                                get { $get_value }
                            }
                        ]>);
                    } else {
                        // lazy evaluation of property value (result is cached)
                        def field_name = Macros.NewSymbol("field_" + prop_name.ToString());
                        ty.Define(<[ decl:
                            private $(field_name : name) : LazyValue[$param_type] = LazyValue(fun() {
                                $get_value
                            });
                        ]>);
                        ty.Define(<[ decl:
                            public $(prop_name : name) : $param_type {
                                get { $(field_name : name).Value }
                            }
                        ]>);
                    }
                | _ => Message.FatalError("Invalid format of property parameter.")
            }
        }
    }

}